Package com.python.pydev.refactoring.markoccurrences

Source Code of com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Apr 29, 2006
*/
package com.python.pydev.refactoring.markoccurrences;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.actions.refactoring.PyRefactorAction;
import org.python.pydev.editor.codefolding.PySourceViewer;
import org.python.pydev.editor.refactoring.RefactoringRequest;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.visitors.scope.ASTEntry;

import com.python.pydev.PydevPlugin;
import com.python.pydev.refactoring.refactorer.AstEntryRefactorerRequestConstants;
import com.python.pydev.refactoring.ui.MarkOccurrencesPreferencesPage;
import com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint;

/**
* This is a 'low-priority' thread. It acts as a singleton. Requests to mark the occurrences
* will be forwarded to it, so, it should sleep for a while and then check for a request.
*
* If the request actually happened, it will go on to process it, otherwise it will sleep some more.
*
* @author Fabio
*/
public class MarkOccurrencesJob extends Job {

    private static final boolean DEBUG = false;
    private static MarkOccurrencesJob singleton;

    /**
     * Make it thread safe
     */
    private static volatile long lastRequestTime = -1;

    /**
     * This is the editor to be analyzed
     */
    private WeakReference<PyEdit> editor;

    /**
     * This is the request time for this job
     */
    private long currRequestTime = -1;

    /**
     * The selection when the occurrences job was requested
     */
    private PySelection ps;

    private MarkOccurrencesJob(WeakReference<PyEdit> editor, PySelection ps) {
        super("MarkOccurrencesJob");
        setPriority(Job.BUILD);
        setSystem(true);
        this.editor = editor;
        this.ps = ps;
        currRequestTime = System.currentTimeMillis();
    }

    /**
     * Mark if we are still abel to do it by the time we get to the run.
     */
    public IStatus run(IProgressMonitor monitor) {
        if (currRequestTime == -1) {
            return Status.OK_STATUS;
        }
        if (currRequestTime == lastRequestTime) {
            return Status.OK_STATUS;
        }
        lastRequestTime = currRequestTime;

        try {
            final PyEdit pyEdit = editor.get();

            if (pyEdit == null || monitor.isCanceled()) {
                return Status.OK_STATUS;
            }
            try {
                IDocumentProvider documentProvider = pyEdit.getDocumentProvider();
                if (documentProvider == null || monitor.isCanceled()) {
                    return Status.OK_STATUS;
                }

                IAnnotationModel annotationModel = documentProvider.getAnnotationModel(pyEdit.getEditorInput());
                if (annotationModel == null || monitor.isCanceled()) {
                    return Status.OK_STATUS;
                }

                Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean> ret = checkAnnotations(pyEdit,
                        documentProvider, monitor);
                if (pyEdit.cache == null || monitor.isCanceled()) { //disposed (cannot add or remove annotations)
                    return Status.OK_STATUS;
                }

                PySourceViewer viewer = pyEdit.getPySourceViewer();
                if (viewer == null || monitor.isCanceled()) {
                    return Status.OK_STATUS;
                }
                if (viewer.getIsInToggleCompletionStyle() || monitor.isCanceled()) {
                    return Status.OK_STATUS;
                }

                if (ret.o3) {
                    if (!addAnnotations(pyEdit, annotationModel, ret.o1, ret.o2)) {
                        //something went wrong, so, let's remove the occurrences
                        removeOccurenceAnnotations(annotationModel, pyEdit);
                    }
                } else {
                    removeOccurenceAnnotations(annotationModel, pyEdit);
                }
            } catch (OperationCanceledException e) {
                throw e;//rethrow this error...
            } catch (AssertionFailedException e) {
                String message = e.getMessage();
                if (message != null && message.indexOf("The file:") != -1 && message.indexOf("does not exist.") != -1) {
                    //don't even report it (the file was probably removed while we were doing the analysis)
                } else {
                    Log.log(e);
                    Log.log("Error while analyzing the file:" + pyEdit.getIFile());
                }
            } catch (Throwable initialE) {
                //Totally ignore this one
                //                Throwable e = initialE;
                //                int i = 0;
                //                while(e.getCause() != null && e.getCause() != e && i < 30){
                //                    e = e.getCause();
                //                    i++;//safeguard for recursion
                //                }
                //                if(e instanceof BadLocationException){
                //                    //ignore (may have changed during the analysis)
                //                }else{
                //                    Log.log(initialE);
                //                    Log.log("Error while analyzing the file:"+pyEdit.getIFile());
                //                }
            }

        } catch (Throwable e) {
            //            Log.log(e); -- ok, remove this log, as things can happen if the user starts editing after the analysis is requested
        }
        return Status.OK_STATUS;
    }

    /**
     * @return a tuple with the refactoring request, the processor and a boolean indicating if all pre-conditions succedded.
     * @throws MisconfigurationException
     */
    private Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean> checkAnnotations(PyEdit pyEdit,
            IDocumentProvider documentProvider, IProgressMonitor monitor) throws BadLocationException,
            OperationCanceledException, CoreException, MisconfigurationException {
        if (!MarkOccurrencesPreferencesPage.useMarkOccurrences()) {
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        //now, let's see if the editor still has a document (so that we still can add stuff to it)
        IEditorInput editorInput = pyEdit.getEditorInput();
        if (editorInput == null) {
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        if (documentProvider.getDocument(editorInput) == null) {
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        if (pyEdit.getSelectionProvider() == null) {
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        //ok, the editor is still there wit ha document... move on
        PyRefactorAction pyRefactorAction = getRefactorAction(pyEdit);

        final RefactoringRequest req = getRefactoringRequest(pyEdit, pyRefactorAction, this.ps);

        if (req == null || !req.nature.getRelatedInterpreterManager().isConfigured()) { //we check if it's configured because it may still be a stub...
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        PyRenameEntryPoint processor = new PyRenameEntryPoint(req);
        //to see if a new request was not created in the meantime (in which case this one will be cancelled)
        if (currRequestTime != lastRequestTime || monitor.isCanceled()) {
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
        }

        try {
            processor.checkInitialConditions(monitor);
            if (currRequestTime != lastRequestTime || monitor.isCanceled()) {
                return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
            }

            processor.checkFinalConditions(monitor, null);
            if (currRequestTime != lastRequestTime || monitor.isCanceled()) {
                return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(null, null, false);
            }

            //ok, pre-conditions suceeded
            return new Tuple3<RefactoringRequest, PyRenameEntryPoint, Boolean>(req, processor, true);
        } catch (Throwable e) {
            throw new RuntimeException("Error in occurrences while analyzing modName:" + req.moduleName
                    + " initialName:" + req.initialName + " line (start at 0):" + req.ps.getCursorLine(), e);
        }
    }

    /**
     * @return true if the annotations were removed and added without any problems and false otherwise
     */
    private synchronized boolean addAnnotations(final PyEdit pyEdit, IAnnotationModel annotationModel,
            final RefactoringRequest req, PyRenameEntryPoint processor) throws BadLocationException {
        HashSet<ASTEntry> occurrences = processor.getOccurrences();
        if (occurrences == null) {
            if (DEBUG) {
                System.out.println("Occurrences == null");
            }
            return false;
        }

        Map<String, Object> cache = pyEdit.cache;
        if (cache == null) {
            return false;
        }

        IDocument doc = pyEdit.getDocument();
        ArrayList<Annotation> annotations = new ArrayList<Annotation>();
        Map<Annotation, Position> toAddAsMap = new HashMap<Annotation, Position>();
        boolean markOccurrencesInStrings = MarkOccurrencesPreferencesPage.useMarkOccurrencesInStrings();

        //get the annotations to add
        for (ASTEntry entry : occurrences) {
            if (!markOccurrencesInStrings) {
                if (entry.node instanceof Name) {
                    Name name = (Name) entry.node;
                    if (name.ctx == Name.Artificial) {
                        continue;
                    }
                }
            }

            SimpleNode node = entry.getNameNode();
            IRegion lineInformation = doc.getLineInformation(node.beginLine - 1);

            try {
                Annotation annotation = new Annotation(PydevPlugin.OCCURRENCE_ANNOTATION_TYPE, false, "occurrence");
                Position position = new Position(lineInformation.getOffset() + node.beginColumn - 1,
                        req.initialName.length());
                toAddAsMap.put(annotation, position);
                annotations.add(annotation);

            } catch (Exception e) {
                Log.log(e);
            }
        }

        //get the ones to remove
        List<Annotation> toRemove = PydevPlugin.getOccurrenceAnnotationsInPyEdit(pyEdit);

        //let other threads execute before getting the lock on the annotation model
        Thread.yield();

        Thread thread = Thread.currentThread();
        int initiaThreadlPriority = thread.getPriority();
        try {
            //before getting the lock, let's execute with normal priority, to optimize the time that we'll
            //retain that object locked (the annotation model is used on lots of places, so, retaining the lock
            //on it on a minimum priority thread is not a good thing.
            thread.setPriority(Thread.NORM_PRIORITY);

            synchronized (getLockObject(annotationModel)) {
                //replace them
                IAnnotationModelExtension ext = (IAnnotationModelExtension) annotationModel;
                ext.replaceAnnotations(toRemove.toArray(new Annotation[0]), toAddAsMap);
            }

        } finally {
            thread.setPriority(initiaThreadlPriority);
        }

        //put them in the pyEdit
        cache.put(PydevPlugin.ANNOTATIONS_CACHE_KEY, annotations);
        return true;
    }

    /**
     * @param pyEdit the editor where we should look for the occurrences
     * @param pyRefactorAction the action that will return the initial refactoring request
     * @param ps the pyselection used (if null it will be created in this method)
     * @return a refactoring request suitable for finding the locals in the file
     * @throws BadLocationException
     * @throws MisconfigurationException
     */
    public static RefactoringRequest getRefactoringRequest(final PyEdit pyEdit, PyRefactorAction pyRefactorAction,
            PySelection ps) throws BadLocationException, MisconfigurationException {
        final RefactoringRequest req = pyRefactorAction.getRefactoringRequest();
        req.ps = ps;
        req.fillInitialNameAndOffset();
        req.inputName = "foo";
        req.setAdditionalInfo(AstEntryRefactorerRequestConstants.FIND_DEFINITION_IN_ADDITIONAL_INFO, false);
        req.setAdditionalInfo(AstEntryRefactorerRequestConstants.FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE, true);
        return req;
    }

    /**
     * @param pyEdit the editor that will have this action
     * @return the action (with the pyedit attached to it)
     */
    public static PyRefactorAction getRefactorAction(PyEdit pyEdit) {
        PyRefactorAction pyRefactorAction = new PyRefactorAction() {

            @Override
            protected String perform(IAction action, IProgressMonitor monitor) throws Exception {
                throw new RuntimeException("Perform should not be called in this case.");
            }
        };
        pyRefactorAction.setEditor(pyEdit);
        return pyRefactorAction;
    }

    /**
     * @param annotationModel
     */
    private synchronized void removeOccurenceAnnotations(IAnnotationModel annotationModel, PyEdit pyEdit) {
        //remove the annotations
        Map<String, Object> cache = pyEdit.cache;
        if (cache == null) {
            return;
        }

        //let other threads execute before getting the lock on the annotation model
        Thread.yield();

        Thread thread = Thread.currentThread();
        int initiaThreadlPriority = thread.getPriority();
        //before getting the lock, let's execute with normal priority, to optimize the time that we'll
        //retain that object locked (the annotation model is used on lots of places, so, retaining the lock
        //on it on a minimum priority thread is not a good thing.
        thread.setPriority(Thread.NORM_PRIORITY);

        try {
            synchronized (getLockObject(annotationModel)) {
                List<Annotation> annotationsToRemove = PydevPlugin.getOccurrenceAnnotationsInPyEdit(pyEdit);

                if (annotationModel instanceof IAnnotationModelExtension) {
                    //replace those
                    ((IAnnotationModelExtension) annotationModel).replaceAnnotations(
                            annotationsToRemove.toArray(new Annotation[annotationsToRemove.size()]), new HashMap());
                } else {
                    Iterator<Annotation> annotationIterator = annotationsToRemove.iterator();

                    while (annotationIterator.hasNext()) {
                        annotationModel.removeAnnotation(annotationIterator.next());
                    }
                }
                cache.put(PydevPlugin.ANNOTATIONS_CACHE_KEY, null);
            }
            //end remove the annotations
        } finally {
            thread.setPriority(initiaThreadlPriority);
        }
    }

    /**
     * Gotten from JavaEditor#getLockObject
     */
    private Object getLockObject(IAnnotationModel annotationModel) {
        if (annotationModel instanceof ISynchronizable)
            return ((ISynchronizable) annotationModel).getLockObject();
        else
            return annotationModel;
    }

    /**
     * This is the function that should be called when we want to schedule a request for
     * a mark occurrences job.
     */
    public static synchronized void scheduleRequest(WeakReference<PyEdit> editor2, PySelection ps) {
        MarkOccurrencesJob j = singleton;
        if (j != null) {
            synchronized (j) {
                j.cancel();
                singleton = null;
            }
        }
        singleton = new MarkOccurrencesJob(editor2, ps);
        singleton.schedule(750);
    }

}
TOP

Related Classes of com.python.pydev.refactoring.markoccurrences.MarkOccurrencesJob

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.